package com.clei.netty.file.client;

import com.clei.consts.NettyConstants;
import com.clei.netty.file.dto.MsgBody;
import com.clei.netty.file.enums.FileCmdTypeEnum;
import com.clei.netty.util.FileDataManager;
import com.clei.netty.util.FilePathManager;
import com.clei.utils.ByteUtil;
import com.clei.utils.CmdUtil;
import com.clei.utils.FileUtil;
import com.clei.utils.ImageUtil;
import com.clei.utils.PrintUtil;
import com.clei.utils.helper.BytesReader;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;

import java.io.File;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * file client handler
 *
 * @author Y
 * @date 2022-04-21
 */
public class FileClientHandler extends SimpleChannelInboundHandler<MsgBody> {

    /**
     * 消息发送者
     */
    private final MsgSender msgSender;

    /**
     * fileName -> fileSize
     */
    public static final Map<String, Long> FILE_NAME_SIZE = new ConcurrentHashMap<>();

    public FileClientHandler(MsgSender msgSender) {
        this.msgSender = msgSender;
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, MsgBody msg) {
        byte cmd = msg.getCmd();
        FileCmdTypeEnum cmdType = FileCmdTypeEnum.getByCmdCode(cmd);
        try {
            if (!FileCmdTypeEnum.RES_DOWNLOAD_DATA.equals(cmdType)) {
                PrintUtil.log("msg : {}", msg);
            }
            switch (cmdType) {
                case RES_UPLOAD_ACK:
                    handleUploadAck(msg);
                    break;
                case RES_UPLOAD_REFUSE:
                case RES_UPLOAD_SUCCESS:
                case RES_UPLOAD_FAIL:
                case RES_DOWNLOAD_REFUSE:
                    String fileName = ByteUtil.toString(msg.getData());
                    PrintUtil.log("cmd : {}, fileName : {}", cmdType.getCmdName(), fileName);
                    break;
                case RES_CATEGORY:
                    handleCategory(msg);
                    break;
                case RES_DOWNLOAD_DATA:
                    handleDownloadData(msg);
                    break;
                case RES_CAPTURE_SCREEN_ACK:
                case RES_CMD_ACK:
                    PrintUtil.log(cmdType.getCmdName());
                    break;
                case REQ_CAPTURE_SCREEN:
                    handleCaptureScreen();
                    break;
                case REQ_CMD:
                    handleCmd(msg);
                    break;
                default:
            }
        } catch (Exception e) {
            PrintUtil.log("请求处理出错 msg : {}", msg, e);
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        PrintUtil.log("[{}]异常关闭", ctx.channel().remoteAddress(), cause);
        // 遇到异常就关闭连接
        ctx.close();
    }

    /**
     * 上传文件数据
     *
     * @param fileName 文件名
     * @param fileId   文件id
     */
    public void uploadFile(String fileName, int fileId) {
        String filePath = FilePathManager.get(fileName);
        try {
            File file = new File(filePath);
            if (!file.exists() || file.isDirectory()) {
                PrintUtil.log("文件不存在或类型错误！ filePath : {}", filePath);
            } else {
                FILE_NAME_SIZE.put(fileName, file.length());
                byte[] fileIdBytes = ByteUtil.intTo4Byte(fileId);
                // 数据 = fileId + index + bytes[]
                byte cmdCode = FileCmdTypeEnum.REQ_UPLOAD_DATA.getCmdCode();
                FileUtil.bytesHandle(file, NettyConstants.FILE_BUFFER_SIZE,
                        (index, bytes) -> {
                            byte[] indexBytes = ByteUtil.intTo4Byte(index);
                            msgSender.sendMsg(cmdCode, ByteUtil.merge(fileIdBytes, indexBytes, bytes));
                        });
                FilePathManager.remove(fileName);
                PrintUtil.log("上传文件数据传输完成 filePath : {}", filePath);
            }
        } catch (Exception e) {
            PrintUtil.log("上传文件数据传输出错 filePath : {}", filePath, e);
        }
    }

    /**
     * 处理上传确认
     *
     * @param msg 消息体
     */
    private void handleUploadAck(MsgBody msg) {
        BytesReader br = new BytesReader(msg.getData());
        int fileId = br.readInt4();
        String fileName = ByteUtil.toString(br.readLast());
        PrintUtil.log("开始上传文件 fileName : {}", fileName);
        uploadFile(fileName, fileId);
    }

    /**
     * 处理文件目录响应
     *
     * @param msg 消息体
     */
    private void handleCategory(MsgBody msg) {
        BytesReader br = new BytesReader(msg.getData());
        int count = br.readInt4();
        Map<String, Long> map = new LinkedHashMap<>(count);
        for (int i = 0; i < count; i++) {
            long fileSize = br.readLong8();
            int fileNameSize = br.readInt4();
            String fileName = ByteUtil.toString(br.read(fileNameSize));
            map.put(fileName, fileSize);
            FILE_NAME_SIZE.putAll(map);
        }
        PrintUtil.log("文件目录");
        map.forEach((k, v) -> PrintUtil.log("{}\t{}", k, v));
    }

    /**
     * 处理下载数据
     *
     * @param msg 消息体
     */
    private void handleDownloadData(MsgBody msg) {
        BytesReader br = new BytesReader(msg.getData());
        int fileId = br.readInt4();
        int index = br.readInt4();
        FileDataManager.add(fileId, index, br.readLast());
    }

    /**
     * 处理截屏上传
     */
    private void handleCaptureScreen() {
        String filePath = ImageUtil.captureScreen();
        File file = new File(filePath);
        if (!file.exists() || file.isDirectory()) {
            PrintUtil.log("文件不存在或类型错误！ filePath : {}", filePath);
        } else {
            String fileName = file.getName();
            long fileSize = file.length();
            FilePathManager.put(fileName, filePath);
            // 文件大小 文件名
            byte[] data = ByteUtil.merge(ByteUtil.longTo8Byte(fileSize), ByteUtil.toBytes(fileName));
            msgSender.sendMsg(FileCmdTypeEnum.REQ_UPLOAD.getCmdCode(), data);
        }
    }

    /**
     * 处理执行命令
     *
     * @param msg 消息体
     */
    private void handleCmd(MsgBody msg) {
        String cmdContent = ByteUtil.toString(msg.getData());
        boolean exp = CmdUtil.exec(cmdContent);
        if (exp) {
            PrintUtil.log("执行命令出错 msg : {}, cmdContent : {}", msg, cmdContent);
        }
    }
}